package io.sundial.job.impl;

import com.google.common.base.Splitter;
import io.sundial.job.JobParameter;
import io.sundial.job.anno.Job;
import io.sundial.job.anno.Option;
import io.sundial.job.anno.Param;
import io.sundial.util.StrKit;

import java.beans.Introspector;
import java.beans.PropertyDescriptor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.LinkedHashMap;
import java.util.Map;

/**
 * 注解化作业
 *
 * @author Payne 646742615@qq.com
 * 2019/1/6 17:25
 */
public abstract class AnnotatedJob extends DocumentedJob {

    @Override
    public String name() {
        Job job = this.getClass().getAnnotation(Job.class);
        return job == null || StrKit.isEmpty(job.value()) ? super.name() : job.value();
    }

    @Override
    public String group() {
        Job job = this.getClass().getAnnotation(Job.class);
        return job == null || StrKit.isEmpty(job.group()) ? super.group() : job.group();
    }

    @Override
    public String type() {
        Job job = this.getClass().getAnnotation(Job.class);
        return job == null || StrKit.isEmpty(job.type()) ? super.type() : job.type();
    }

    @Override
    public String version() {
        Job job = this.getClass().getAnnotation(Job.class);
        return job == null || StrKit.isEmpty(job.version()) ? super.version() : job.version();
    }

    @Override
    public int shardable() {
        Job job = this.getClass().getAnnotation(Job.class);
        return job == null || job.shardable() == 0 ? super.shardable() : job.shardable();
    }

    @Override
    public Map<String, JobParameter> parameters() {
        Map<String, JobParameter> parameters = super.parameters();

        try {
            Class<? extends io.sundial.job.Job> jobClass = this.getClass();
            PropertyDescriptor[] properties = Introspector.getBeanInfo(jobClass).getPropertyDescriptors();
            for (PropertyDescriptor property : properties) {
                String name = property.getName();
                if (name.equals("class")) continue;

                JobParameter parameter = parameters.get(name);
                if (parameter == null) {
                    continue;
                }

                Method getter = property.getReadMethod();
                Method setter = property.getWriteMethod();
                if (getter == null || setter == null) continue;

                Param param = getter.isAnnotationPresent(Param.class)
                        ? getter.getAnnotation(Param.class)
                        : setter.isAnnotationPresent(Param.class)
                        ? setter.getAnnotation(Param.class)
                        : null;

                Class<?> superclass = jobClass;
                while (param == null && superclass != Object.class) {
                    try {
                        Field field = superclass.getDeclaredField(name);
                        param = field.getAnnotation(Param.class);
                        break;
                    } catch (Exception ignored) {
                        superclass = superclass.getSuperclass();
                    }
                }

                if (param == null) {
                    continue;
                }

                if (StrKit.isNotEmpty(param.value())) {
                    parameter.setLabel(param.value());
                }
                if (StrKit.isNotEmpty(param.editor())) {
                    parameter.setEditor(param.editor());
                }
                if (StrKit.isNotEmpty(param.option())) {
                    Map<String, String> options = new LinkedHashMap<>();
                    Iterable<String> entries = Splitter.on(',').split(param.option());
                    for (String entry : entries) {
                        int index = entry.indexOf(':');
                        if (index < 0) throw new IllegalArgumentException(param.option() + " is not a valid job parameter options expression");
                        options.put(entry.substring(0, index).trim(), entry.substring(index + 1).trim());
                    }
                    parameter.setOptions(options);
                }
                if (param.options().length > 0) {
                    Map<String, String> options = new LinkedHashMap<>();
                    for (Option option : param.options()) options.put(option.value(), option.label());
                    parameter.setOptions(options);
                }
                if (StrKit.isNotEmpty(param.description())) {
                    parameter.setDescription(param.description());
                }
            }
        } catch (Exception e) {
            throw new IllegalStateException(e);
        }

        return parameters;
    }

    @Override
    public String description() {
        Job job = this.getClass().getAnnotation(Job.class);
        return job == null || StrKit.isEmpty(job.description()) ? super.description() : job.description();
    }
}
